package edu.cicese.alz;

import edu.cicese.alz.swing.CustomToggleButton;
import edu.cicese.alz.swing.SwingUtil;
import edu.cicese.alz.swing.ToggleButtonMouseListener;
import edu.cicese.alz.swing.tables.UploadDSVTable;
import edu.cicese.alz.util.PropertyUtil;
import edu.cicese.alz.util.Utilities;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.Box;
import javax.swing.ImageIcon;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;
import java.awt.Color;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.io.File;
import java.io.FilenameFilter;
import java.io.RandomAccessFile;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Map;

/**
 * Created by: Eduardo Quintana Contreras
 * Date: 29/05/12
 * Time: 11:53 AM
 */
public class FrameUploadDSV extends SwingComponent {
	// Database
	public Connection connection;
	public Statement statement;
	public ResultSet resultSet;

	private Map<Integer, Subject> idxSubjects = new HashMap();

	private Object[] tbIdentifiers = new Object[]{"Upload", "User ID", "Name", "ACC", "GPS", "NFC", "WIFI"};
	private UploadDSVTable tblSubject = new UploadDSVTable(idxSubjects);
	private DefaultTableModel dtmSubjects;

	private JComboBox<String> cmbCriteria;
	private JTextField txtSearch;
	private JScrollPane spSubject;

	private JButton btnUpload, btnCancel;
	private CustomToggleButton btnAccelerometer, btnGPS, btnWIFI, btnNFC, btnSelectAll;
	private boolean[] globalDSVFiles;

	private String uploadedDir;

	public FrameUploadDSV() {
		super(600, 430, "Upload DSV files");

		initComponents();
//		fillSubjectsDemo();
		fillSubjects(Query.getSubjects());
	}

	private void initComponents() {
		dtmSubjects = new DefaultTableModel() {
			public Class getColumnClass(int col) {
				if (col != 1 && col != 2) {
					return Boolean.class;
				}
				return super.getColumnClass(col);
			}
		};
		dtmSubjects.addTableModelListener(new TableModelListener() {
			@Override
			public void tableChanged(TableModelEvent e) {
				subjectChanged(e.getFirstRow(), e.getColumn());
			}
		});

		dtmSubjects.setColumnIdentifiers(tbIdentifiers);
		tblSubject.setModel(dtmSubjects);
		tblSubject.setRowHeight(20);
		tblSubject.setGridColor(new Color(212, 214, 217));
		tblSubject.getTableHeader().setReorderingAllowed(false);
		tblSubject.getTableHeader().setBackground(SwingUtil.cTableHeader);
		tblSubject.getTableHeader().setFont(SwingUtil.fontTableHeader);


		int[] columnSizes = new int[]{20, 50, 180, 20, 20, 20, 20};
		for (int c = 0; c < columnSizes.length; c++) {
			TableColumn column = tblSubject.getColumnModel().getColumn(c);
			column.setPreferredWidth(columnSizes[c]);
			column.setResizable(false);

		}
		tblSubject.setFillsViewportHeight(true);

		spSubject = new JScrollPane(tblSubject);

		btnUpload = createButton("", "Upload data to database", "b_upload");
		btnUpload.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				actionUpload();
			}
		});

		btnCancel = createButton("", "Cancel", "b_cancel");
		btnCancel.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				actionCancel();
			}
		});

		btnSelectAll = createCustomToggleButton("", "Select all subjects", "b_select_all");
		btnSelectAll.addMouseListener(new ToggleButtonMouseListener(btnSelectAll));
		btnSelectAll.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				actionSelectAll();
			}
		});

		btnAccelerometer = createCustomToggleButton("", "Upload all Accelerometer data", "b_acc");
		btnAccelerometer.addMouseListener(new ToggleButtonMouseListener(btnAccelerometer));
		btnAccelerometer.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				actionAccelerometer();
			}
		});

		btnGPS = createCustomToggleButton("", "Upload all GPS data", "b_gps");
		btnGPS.addMouseListener(new ToggleButtonMouseListener(btnGPS));
		btnGPS.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				actionGPS();
			}
		});

		btnWIFI = createCustomToggleButton("", "Upload all NFC data", "b_nfc");
		btnWIFI.addMouseListener(new ToggleButtonMouseListener(btnWIFI));
		btnWIFI.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				actionWIFI();
			}
		});

		btnNFC = createCustomToggleButton("", "Upload all WIFI data", "b_wifi");
		btnNFC.addMouseListener(new ToggleButtonMouseListener(btnNFC));
		btnNFC.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				actionNFC();
			}
		});

		Box boxPaneButtons = Box.createVerticalBox();
		boxPaneButtons.add(btnUpload);
		boxPaneButtons.add(btnCancel);
		boxPaneButtons.add(btnSelectAll);
		boxPaneButtons.add(btnAccelerometer);
		boxPaneButtons.add(btnGPS);
		boxPaneButtons.add(btnNFC);
		boxPaneButtons.add(btnWIFI);


		txtSearch = SwingUtil.createTextField(150, 24);
		txtSearch.setFont(new Font("Calibri", Font.PLAIN, 14));

		cmbCriteria = SwingUtil.createComboBox(new Object[]{"Name", "User ID"}, 70, 24);

		JButton btnSearch = createButton("Search", "Search subject(s)", "search", 66, 24);
		btnSearch.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				actionSearch();
			}
		});

		JButton btnShowAll = createButton("Show all", "Show all subjects", "show_all", 76, 24);
		btnShowAll.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				actionShowAll();
			}
		});

		Box boxSearch = Box.createHorizontalBox();
		boxSearch.add(Box.createHorizontalGlue());
		boxSearch.add(txtSearch);
		boxSearch.add(Box.createHorizontalStrut(5));
		boxSearch.add(cmbCriteria);
		boxSearch.add(Box.createHorizontalStrut(5));
		boxSearch.add(btnSearch);
		boxSearch.add(Box.createHorizontalStrut(5));
		boxSearch.add(btnShowAll);
		boxSearch.add(Box.createHorizontalGlue());


		JPanel pContent = new JPanel(new GridBagLayout());
		GridBagConstraints gbc = new GridBagConstraints();

		// Row 1
		gbc.insets = new Insets(5, 5, 0, 5);
		gbc.gridx = 0;
		gbc.gridy = 0;
		pContent.add(boxSearch, gbc);

		// Row 2
		gbc.fill = GridBagConstraints.BOTH;
		gbc.insets = new Insets(5, 5, 5, 5);
		gbc.gridx = 0;
		gbc.gridy = 1;
		gbc.weightx = 0.5;
		gbc.weighty = 0.5;
		gbc.anchor = GridBagConstraints.FIRST_LINE_START;
		pContent.add(spSubject, gbc);

		gbc.insets = new Insets(5, 0, 5, 5);
		gbc.gridx = 1;
		gbc.gridy = 1;
		gbc.weightx = 0;
		gbc.weighty = 0;
		gbc.fill = GridBagConstraints.NONE;
		pContent.add(boxPaneButtons, gbc);


		pContent.setBackground(SwingUtil.cBackground);
		setContentPane(pContent);
		pack();

		InputMap inputMap = tblSubject.getInputMap(JTable.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
		ActionMap actionMap = tblSubject.getActionMap();

		Action actionAdd = new AbstractAction() {
			public void actionPerformed(ActionEvent e) {
				selectSubject(tblSubject.getSelectedRows(), true);
			}
		};

		Action actionRemove = new AbstractAction() {
			public void actionPerformed(ActionEvent e) {
				selectSubject(tblSubject.getSelectedRows(), false);
			}
		};

		inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_INSERT, 0), "select");
		actionMap.put("select", actionAdd);
		inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), "remove");
		actionMap.put("remove", actionRemove);



//		resetButtons();
	}

	private void actionUpload() {
		showLoadDialog();
	}

	private void uploadDSVFiles() {
		long uploadedTimestamp = System.currentTimeMillis();
		for (int row = 0; row < dtmSubjects.getRowCount(); row++) {
			int userID = (Integer) dtmSubjects.getValueAt(row, 1);
			String dsvDir = PropertyUtil.getInfileDir(userID);
			uploadedDir = PropertyUtil.getUploadedDir(dsvDir, uploadedTimestamp);
			//TODO create dir only when needed
			new File(uploadedDir).mkdirs();
			if ((Boolean) dtmSubjects.getValueAt(row, 0)) {
				if ((Boolean) dtmSubjects.getValueAt(row, 3)) {
					uploadAccelerometerDsv(userID, dsvDir);
				}
				if ((Boolean) dtmSubjects.getValueAt(row, 4)) {
					uploadGPSDsv(dsvDir);
				}
				if ((Boolean) dtmSubjects.getValueAt(row, 5)) {
					uploadNFCDsv(dsvDir);
				}
				if ((Boolean) dtmSubjects.getValueAt(row, 6)) {
					uploadWIFIDsv(dsvDir);
				}
			}
		}
		fillSubjects(Query.getSubjects());
	}

	private void uploadAccelerometerDsv(int userID, String dsvDir) {
		File dir = new File(dsvDir);
		FilenameFilter filenameFilter = new FilenameFilter() {
			public boolean accept(File dir, String name) {
				return name.startsWith("acc") && name.endsWith(".dsv");
			}
		};
		File[] files = dir.listFiles(filenameFilter);
		if (files != null && files.length > 0) {
			// for each accelerometer dsv file (each table)
			for (File file : files) {
				String name = file.getName();
				String dsvPath = file.getPath();
				int tableID = Integer.valueOf(name.substring(name.lastIndexOf("_") + 1, name.indexOf(".")));

				int tableCount = 0;
				ResultSet resultSet = Query.getUserTables(userID);
				if (resultSet != null) {
					try {
						if (resultSet.next()) {
							tableCount = resultSet.getInt(1);
						}
					} catch (SQLException e) {
						e.printStackTrace();
					}
				}
				Query.close();

				if (tableID > tableCount) {
					Query.createAccelerometerTable(userID, tableID);
				}

				TableLimit tableLimit = getTableLimitTimestamps(userID, tableID, dsvPath);
				Query.loadAccelerometerDsv(userID, tableID, dsvPath, tableLimit.getTimestamp1(), tableLimit.getTimestamp2(), tableLimit.getCount(), tableLimit.isNewTableLimit());

				// move dsv file
				if (!Utilities.moveFile(dsvPath, uploadedDir + name)) {
					Utilities.printLog("Cannot delete file " + dsvPath + " because it's being used by another person or program.");
				}
			}
		}
	}

	private void uploadGPSDsv(String dsvDir) {
		String gpsFile = dsvDir + "gps.dsv";
		if (new File(gpsFile).exists()) {
			Query.executeQuery(Query.queryLoadGPSDsv(gpsFile));
		}
		if (!Utilities.moveFile(gpsFile, uploadedDir + "gps.dsv")) {
			Utilities.printLog("Cannot delete file " + gpsFile + " because it's being used by another person or program.");
		}
	}

	private void uploadNFCDsv(String dsvDir) {
		String nfcFile = dsvDir + "nfc.dsv";
		if (new File(nfcFile).exists()) {
			Query.executeQuery(Query.queryLoadNFCDsv(nfcFile));
		}
		if (!Utilities.moveFile(nfcFile, uploadedDir + "nfc.dsv")) {
			Utilities.printLog("Cannot delete file " + nfcFile + " because it's being used by another person or program.");
		}
		//TODO create NFCTags missing.
	}

	private void uploadWIFIDsv(String dsvDir) {
		String wifiFile = dsvDir + "wifi.dsv";
		if (new File(wifiFile).exists()) {
			Query.executeQuery(Query.queryLoadWIFIDsv(wifiFile));
		}
		if (!Utilities.moveFile(wifiFile, uploadedDir + "wifi.dsv")) {
			Utilities.printLog("Cannot delete file " + wifiFile + " because it's being used by another person or program.");
		}
	}

	private TableLimit getTableLimitTimestamps(int userID, int tableID, String dsvPath) {
		TableLimit tableLimit = null;
		try {
			int count;
			int fileCount = Utilities.getLineCount(new File(dsvPath));
			long timestamp1, timestamp2;
			boolean newTableLimit = true;
			ResultSet resultSet = Query.executeQuery(Query.querySelectTableLimit(userID, tableID));
			// if not null (i.e. accelerometer table already exists) get timestamp 1 from database
			if (resultSet.next()) {
				count = resultSet.getInt(5) + fileCount;
				timestamp1 = resultSet.getLong(3);
				newTableLimit = false;
			}
			else {
				// if null (i.e. new accelerometer table and new entry in table_limit) get timestamp 1 from dsv file
				count = fileCount;
				timestamp1 = Long.valueOf(Utilities.getFirstLine(new File(dsvPath)).split(";")[3]);
			}
			timestamp2 = Long.valueOf(Utilities.getLastLine(new File(dsvPath)).split(";")[3]);

			tableLimit = new TableLimit(timestamp1, timestamp2, count, newTableLimit);
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			Query.close(connection, statement, resultSet);
			return tableLimit;
		}
	}

	private void actionCancel() {
		//TODO reset values?
		closeWindow(true);
	}

	private void actionSelectAll() {
		selectCheckBoxes(btnSelectAll, 0);
	}

	private void actionAccelerometer() {
		selectCheckBoxes(btnAccelerometer, 3);
	}

	private void actionGPS() {
		selectCheckBoxes(btnGPS, 4);
	}

	private void actionNFC() {
		selectCheckBoxes(btnNFC, 5);
	}

	private void actionWIFI() {
		selectCheckBoxes(btnWIFI, 6);
	}

	private void selectCheckBoxes(CustomToggleButton btn, int col) {
		boolean selected = btn.isSelected();
		for (int row = 0; row < dtmSubjects.getRowCount(); row++) {
			if (col == 0 || idxSubjects.get(row).hasDSVFile(col - 3)) {
				dtmSubjects.setValueAt(!selected, row, col);
			}
		}
	}

	private void fillSubjects(ResultSet resultSet) {
		dtmSubjects.setRowCount(0);
		idxSubjects.clear();
		String idpDir = PropertyUtil.getIpdDir();
		System.out.println(idpDir);
		try {
			globalDSVFiles = new boolean[]{false, false, false, false};
			while (resultSet.next()) {
				int userID = resultSet.getInt(1);
				boolean[] dsvFiles = hasDSVFiles(userID, idpDir);
				if (dsvFiles[0] || dsvFiles[1] || dsvFiles[2] || dsvFiles[3]) {
					String name = resultSet.getString(2);
					idxSubjects.put(dtmSubjects.getRowCount(), new Subject(userID, name, true, dsvFiles));
					dtmSubjects.addRow(new Object[]{true, userID, name, dsvFiles[0], dsvFiles[1], dsvFiles[2], dsvFiles[3]});
					setGlobalDSVFiles(dsvFiles);
				}
			}
			resetButtons();
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			Query.close(connection, statement, resultSet);
		}
	}

	private void setGlobalDSVFiles(boolean[] dsvFiles) {
		for (int i = 0; i < dsvFiles.length; i++) {
			if (dsvFiles[i]) {
				globalDSVFiles[i] = true;
			}
		}
	}

	private void resetButtons() {
		if (!idxSubjects.isEmpty()) {
			btnSelectAll.setSelected(true);
//			btnSelectAll.select();
		}

		btnAccelerometer.setSelected(false);
		btnGPS.setSelected(false);
		btnNFC.setSelected(false);
		btnWIFI.setSelected(false);
//		btnAccelerometer.deselect();
//		btnGPS.deselect();
//		btnNFC.deselect();
//		btnWIFI.deselect();

		btnAccelerometer.setEnabled(globalDSVFiles[0]);
		btnGPS.setEnabled(globalDSVFiles[1]);
		btnNFC.setEnabled(globalDSVFiles[2]);
		btnWIFI.setEnabled(globalDSVFiles[3]);
	}

	private void fillSubjectsDemo() {
		idxSubjects.clear();
		String idpDir = PropertyUtil.getProperty(PropertyUtil.HOME_DIR) + Utilities.getSystemSeparator();
		for (int userID = 1; userID < 31; userID++) {
			String name = "anon " + userID;
			idxSubjects.put(dtmSubjects.getRowCount(), new Subject(userID, name, true, new boolean[]{false, true, false, false}));
			dtmSubjects.addRow(new Object[]{true, userID, name, false, true, false, false});
		}
	}

	private boolean[] hasDSVFiles(int userID, String idpDir) {
		boolean[] dsvFiles = new boolean[]{false, false, false, false}; //0:ACC 1:GPS 2:NFC 3:WIFI

		File dsvDir = new File(idpDir + "users" + Utilities.getSystemSeparator() + userID + Utilities.getSystemSeparator() + "infiles" + Utilities.getSystemSeparator());
		FilenameFilter filenameFilter = new FilenameFilter() {
			public boolean accept(File dir, String name) {
				return name.endsWith(".dsv");
			}
		};
		File[] files = dsvDir.listFiles(filenameFilter);
		if (files != null && files.length > 0) {
			for (File file : files) {
				String name = file.getName();
				if (name.startsWith("acc")) {
					dsvFiles[0] = true;
				}
				switch (file.getName()) {
					case "gps.dsv":
						dsvFiles[1] = true;
						break;
					case "nfc.dsv":
						dsvFiles[2] = true;
						break;
					case "wifi.dsv":
						dsvFiles[3] = true;
						break;
				}
			}
		}
		return dsvFiles;
	}

	private void actionSearch() {
		String txt = txtSearch.getText().trim();
		if (!txt.equals("")) {
			if (cmbCriteria.getSelectedIndex() == 0) {
				dtmSubjects.setNumRows(0);
				fillSubjects(Query.getSubjects(txtSearch.getText().trim()));
			} else {
				try {
					int userID = Integer.parseInt(txt);
					dtmSubjects.setNumRows(0);
					fillSubjects(Query.getSubjects(userID));
				} catch (NumberFormatException nfe) {
					JOptionPane.showMessageDialog(null, "Please enter an Integer value", "Data Input Error", JOptionPane.ERROR_MESSAGE);
				}
			}
		} else {
			System.out.println("Enter name o user ID.");
		}
	}

	private void actionShowAll() {
		dtmSubjects.setNumRows(0);
		fillSubjects(Query.getSubjects());
	}

	private void showLoadDialog() {
		if (JOptionPane.showConfirmDialog(this,
				"This action will update the database\n and you won't be able to undo these changes\nContinue?",
				"Load DSV files", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE,
				new ImageIcon(getClass().getClassLoader().getResource("icons/upload.png")))
				== JOptionPane.YES_OPTION) {
			uploadDSVFiles();
		}
	}

	private void selectSubject(int[] rowsSelected, boolean select) {
		for (int rowSelected : rowsSelected) {
			idxSubjects.get(rowSelected).setSelected(select);
			dtmSubjects.setValueAt(select, rowSelected, 0);
		}
	}

	private void subjectChanged(int row, int col) {
//		Subject subject = idxSubjects.get(row);
//		switch (col) {
//			case 0:
//				boolean selected = (Boolean) dtmSubjects.getValueAt(row, 0);
//				subject.setSelected(selected);
//				if (!selected) {
//					btnSelectAll.setSelected(false);
//				} else {
//					//TODO Check if all subjects are select, if so, select btnSelectAll, if not, deselect it
//				}
//				break;
//
//		}
	}

	public boolean openConnection() {
		try {
			// This will load the MySQL driver, each DB has its own driver
			Class.forName("com.mysql.jdbc.Driver");
			// Setup the connection with the DB
			connection = DriverManager.getConnection("jdbc:mysql://localhost/alzheimerdb", "root", "alz");
			// Statements allow to issue SQL queries to the database
			statement = connection.createStatement();
			return true;
		} catch (ClassNotFoundException | SQLException e) {
			e.printStackTrace();
			return false;
		}
	}

	public String tail(File file) {
		try {
			RandomAccessFile fileHandler = new RandomAccessFile(file, "r");
			long fileLength = file.length() - 1;
			StringBuilder sb = new StringBuilder();

			for (long filePointer = fileLength; filePointer != -1; filePointer--) {
				fileHandler.seek(filePointer);
				int readByte = fileHandler.readByte();

				if (readByte == 0xA) {
					if (filePointer == fileLength) {
						continue;
					} else {
						break;
					}
				} else if (readByte == 0xD) {
					if (filePointer == fileLength - 1) {
						continue;
					} else {
						break;
					}
				}

				sb.append((char) readByte);
			}

			String lastLine = sb.reverse().toString();
			return lastLine;
		} catch (java.io.FileNotFoundException e) {
			e.printStackTrace();
			return null;
		} catch (java.io.IOException e) {
			e.printStackTrace();
			return null;
		}
	}

	public static void main(String[] a) {
		SwingUtil.setUIManager();
		new FrameUploadDSV();
	}


	private class TableLimit {
		private long timestamp1, timestamp2;
		private int count;
		private boolean newTableLimit;

		private TableLimit(long timestamp1, long timestamp2, int count, boolean newTableLimit) {
			this.timestamp1 = timestamp1;
			this.timestamp2 = timestamp2;
			this.count = count;
			this.newTableLimit = newTableLimit;
		}

		public long getTimestamp1() {
			return timestamp1;
		}

		public long getTimestamp2() {
			return timestamp2;
		}

		public int getCount() {
			return count;
		}

		public boolean isNewTableLimit() {
			return newTableLimit;
		}
	}
}
